import json
import os
import time
import asyncio
import threading
import uuid
import socket
import hashlib
import platform
from loguru import logger


class StateManager:
    """
    统一状态管理器：
    - 线程安全
    - 自动保存
    - 异步友好
    - 支持登录状态机（未登录 / 登录中 / 已登录 / 登录失败）
    """
    
    @staticmethod
    def _generate_machine_id():
        """
        基于机器硬件信息生成唯一ID
        确保每台服务器都有不同的ID，即使复制了相同的代码
        
        组合以下信息：
        1. 主机名
        2. MAC地址（网卡物理地址）
        3. 机器UUID（Linux: /etc/machine-id）
        
        返回: 格式如 "agent_abc123def456"
        """
        identifiers = []
        
        # 1. 主机名
        try:
            hostname = socket.gethostname()
            identifiers.append(hostname)
        except:
            pass
        
        # 2. MAC地址（获取第一个非lo接口的MAC）
        try:
            import uuid as uuid_module
            mac = uuid_module.getnode()
            mac_str = ':'.join(('%012X' % mac)[i:i+2] for i in range(0, 12, 2))
            identifiers.append(mac_str)
        except:
            pass
        
        # 3. 机器UUID（Linux）
        if platform.system() == "Linux":
            for path in ["/etc/machine-id", "/var/lib/dbus/machine-id"]:
                try:
                    with open(path, "r") as f:
                        machine_id = f.read().strip()
                        identifiers.append(machine_id)
                        break
                except:
                    continue
        
        # 4. 如果所有方法都失败，使用随机UUID（极少见）
        if not identifiers:
            identifiers.append(str(uuid.uuid4()))
        
        # 组合所有标识并生成MD5哈希
        combined = "_".join(identifiers)
        hash_obj = hashlib.md5(combined.encode())
        unique_id = hash_obj.hexdigest()[:12]  # 取前12位
        
        return f"agent_{unique_id}"
    
    def __init__(self, path="state.json", autosave=True):
        self.path = os.path.join(os.getcwd(), path)
        self.state = {
            # 客户端标识（持久化，用于控制端识别）
            "client_id": str(uuid.uuid4()),  # 会在_load()中被加载的值覆盖（如果存在）
            "hostname": socket.gethostname(),  # 主机名，方便识别
            
            # 在线和连接状态
            "online": True,             # 在线状态（TCP心跳用）
            "connected": False,         # TCP连接状态
            "last_heartbeat": time.time(),
            
            # 浏览器状态
            "browser_status": "stopped",  # stopped / starting / running / error
            
            # 登录状态
            "login_status": "not_logged_in",  # not_logged_in / logging_in / logged_in / login_error
            "account": None,            # 当前账号名（userId）
            "cookie": None,             # 登录cookie，可选保存
            
            # 工作状态
            "work_status": "idle",      # idle / searching / ordering / error
            "client_type": None,        # search / order (客户端类型，由控制端设置)
            
            # 账号封禁状态
            "banned": False,            # 是否被封禁
            "banned_at": None,           # 封禁开始时间（时间戳）
            "last_ban_check": None,     # 最后检测封禁状态的时间（时间戳）
            "last_session_refresh_time": 0,  # 最后一次Session恢复时间（时间戳）
            
            # 风控相关
            "consecutive_risk_count": 0,  # 连续风控次数
            "last_risk_time": None,       # 最后一次风控时间（时间戳）
            
            # 错误和日志
            "last_error": None,         # 最近错误
            "error_count": 0,           # 错误计数
        }
        self._lock = threading.Lock()
        self._autosave = autosave
        self._load()
        
        # 强制重置非持久化状态（Agent重启时应该重新初始化的状态）
        with self._lock:
            self.state["browser_status"] = "stopped"
            self.state["connected"] = False
            self.state["work_status"] = "idle"
            self.state["last_error"] = None
            self.state["hostname"] = socket.gethostname()  # 每次启动更新主机名
            logger.info("Agent状态已重置（browser_status=stopped, connected=False, work_status=idle）")
        
        # 确保client_id存在并持久化
        # ⚠️ 重要：每次启动都检测当前机器ID，防止复制项目导致ID冲突
        current_machine_id = self._generate_machine_id()
        saved_id = self.state.get("client_id")
        
        # 如果saved_id与当前机器ID不同，说明：
        # 1. 项目从其他机器复制过来（state.json带着旧ID）
        # 2. 或者首次运行
        # 都应该使用当前机器的ID
        if saved_id != current_machine_id:
            if saved_id:
                logger.warning(f"检测到client_id不匹配（可能是项目被复制）")
                logger.warning(f"  旧ID: {saved_id}")
                logger.warning(f"  新ID: {current_machine_id}")
                logger.warning(f"  已更新为当前机器的ID")
            else:
                logger.info(f"首次运行，生成Agent客户端ID（基于机器硬件）")
            
            self.state["client_id"] = current_machine_id
            self.save()
        
        logger.info(f"Agent客户端ID: {current_machine_id}")

    # ==========================================================
    # 文件操作
    # ==========================================================
    def _load(self):
        """加载本地状态"""
        if not os.path.exists(self.path):
            return
        try:
            with open(self.path, "r", encoding="utf-8") as f:
                data = json.load(f)
                if isinstance(data, dict):
                    # 避免覆盖关键结构
                    self.state.update({k: v for k, v in data.items() if k in self.state})
            logger.info(f"加载状态文件成功: {self.path}")
        except Exception as e:
            logger.warning(f"状态文件加载失败: {e}")

    def save(self):
        """同步保存状态"""
        try:
            with self._lock:
                # 不保存临时状态
                state_to_save = {k: v for k, v in self.state.items() if k not in ["last_error"]}
                with open(self.path, "w", encoding="utf-8") as f:
                    json.dump(state_to_save, f, indent=2, ensure_ascii=False)
            # ✅ 移除冗余日志：状态保存是高频操作，不需要每次都记录
        except Exception as e:
            logger.warning(f"状态保存失败: {e}")

    async def save_async(self):
        """异步保存（不会阻塞事件循环）"""
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, self.save)

    # ==========================================================
    # 快捷访问属性
    # ==========================================================
    @property
    def is_login(self):
        return self.state.get("login_status") == "logged_in"

    @property
    def is_logging(self):
        return self.state.get("login_status") == "logging_in"

    @property
    def is_not_login(self):
        return self.state.get("login_status") == "not_logged_in"

    @property
    def is_error(self):
        return self.state.get("login_status") == "login_error"

    @property
    def login_status(self):
        return self.state.get("login_status")

    # ==========================================================
    # 状态更新
    # ==========================================================
    def set(self, key, value):
        """更新单个字段"""
        with self._lock:
            self.state[key] = value
        if self._autosave:
            self.save()

    def update(self, **kwargs):
        """批量更新"""
        with self._lock:
            self.state.update(kwargs)
        if self._autosave:
            self.save()

    def get(self, key, default=None):
        return self.state.get(key, default)

    def to_dict(self, include_cookie=False):
        """返回当前状态快照
        
        Args:
            include_cookie: 是否包含cookie（默认False，减少心跳包大小）
        """
        state_copy = dict(self.state)
        # 默认不包含cookie，减少心跳包大小
        if not include_cookie and 'cookie' in state_copy:
            state_copy.pop('cookie')
        return state_copy

    # ==========================================================
    # 状态流转封装
    # ==========================================================
    def mark_heartbeat(self):
        """更新心跳时间"""
        self.set("last_heartbeat", time.time())

    def start_login(self, account_name=None):
        """开始登录流程"""
        self.update(
            login_status="logging_in",
            account=account_name,
            last_error=None
        )
        logger.info(f"[{account_name or '-'}] 登录流程开始")

    def finish_login(self, success=True, cookie=None):
        """结束登录流程"""
        if success:
            self.update(
                login_status="logged_in", 
                cookie=cookie
            )
            logger.success(f"账号登录成功")
        else:
            self.update(login_status="login_error")
            logger.warning("账号登录失败")

    def set_login_error(self, error_msg=None):
        """标记登录错误"""
        self.update(login_status="login_error", last_error=error_msg)
        logger.error(f"登录出错: {error_msg}")

    def mark_logout(self):
        """
        标记登出
        
        ⚠️ 注意：不清空 cookie，因为：
        1. Session Cookie（cookie2, unb 等）是长期有效的，不会过期
        2. 只有 token（_m_h5_tk）会过期
        3. 保留 Session Cookie 可以在下次启动时通过刷新 token 恢复登录
        4. 如果清空所有 cookie，会导致下次启动必须重新登录
        """
        # ✅ 只清空 token，保留 Session Cookie
        current_cookies = self.get("cookie", {})
        if current_cookies:
            # 只删除 token 相关的 cookie，保留 Session Cookie
            cookies_to_keep = {k: v for k, v in current_cookies.items() 
                               if k not in ['_m_h5_tk', '_m_h5_tk_enc']}
            self.update(login_status="not_logged_in", account=None, cookie=cookies_to_keep)
        else:
            self.update(login_status="not_logged_in", account=None)
        logger.info("账号已登出,非重置浏览器保留cookie")

    # ==========================================================
    # 工作状态管理
    # ==========================================================
    def set_work_status(self, status):
        """设置工作状态"""
        self.set("work_status", status)
        
    def start_searching(self):
        """开始搜索"""
        self.set("work_status", "searching")
        
    def stop_searching(self):
        """停止搜索"""
        self.set("work_status", "idle")
        
    def start_ordering(self):
        """开始下单"""
        self.set("work_status", "ordering")
        
    def stop_ordering(self):
        """停止下单"""
        self.set("work_status", "idle")
    
    def set_error(self, error_msg):
        """设置错误状态"""
        with self._lock:
            self.state["work_status"] = "error"
            self.state["last_error"] = error_msg
            self.state["error_count"] = self.state.get("error_count", 0) + 1
        if self._autosave:
            self.save()
    
    def clear_error(self):
        """清除错误状态"""
        self.update(work_status="idle", last_error=None)

    # ==========================================================
    # 封禁状态管理
    # ==========================================================
    def mark_banned(self):
        """标记账号被封禁"""
        current_time = time.time()
        with self._lock:
            self.state["banned"] = True
            # 如果之前没有封禁时间，设置封禁开始时间
            if not self.state.get("banned_at"):
                self.state["banned_at"] = current_time
            self.state["last_ban_check"] = current_time
        if self._autosave:
            self.save()
        logger.warning(f"账号已标记为封禁状态（封禁开始时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.state.get('banned_at')))}）")
    
    def clear_banned(self):
        """清除封禁状态"""
        with self._lock:
            self.state["banned"] = False
            self.state["banned_at"] = None
            self.state["last_ban_check"] = time.time()
            # ✅ 清除封禁状态时，如果work_status是banned，重置为idle
            if self.state.get("work_status") == "banned":
                self.state["work_status"] = "idle"
        if self._autosave:
            self.save()
        logger.success("账号封禁状态已清除")
    
    def update_ban_check_time(self):
        """更新最后检测时间"""
        with self._lock:
            self.state["last_ban_check"] = time.time()
        if self._autosave:
            self.save()
    
    def is_banned(self) -> bool:
        """检查是否被封禁"""
        return self.state.get("banned", False)
    
    def get_ban_remaining_time(self) -> float:
        """
        获取距离下次检测的剩余时间（秒）
        如果距离上次检测已满8小时，返回0（应立即检测）
        """
        last_check = self.state.get("last_ban_check")
        if not last_check:
            return 0  # 从未检测过，立即检测
        
        elapsed = time.time() - last_check
        check_interval = 8 * 3600  # 8小时
        remaining = max(0, check_interval - elapsed)
        return remaining
    
    def should_check_ban_status(self) -> bool:
        """判断是否应该检测封禁状态（距离上次检测已满8小时）"""
        remaining = self.get_ban_remaining_time()
        return remaining == 0
    
    def get_ban_message(self) -> str:
        """获取封禁提示消息，包含剩余时间"""
        if not self.is_banned():
            return ""
        
        banned_at = self.state.get("banned_at")
        if banned_at:
            # 计算封禁时长
            elapsed = time.time() - banned_at
            hours = int(elapsed / 3600)
            minutes = int((elapsed % 3600) / 60)
            
            # 计算距离下次检测的剩余时间
            remaining = self.get_ban_remaining_time()
            remaining_hours = int(remaining / 3600)
            remaining_minutes = int((remaining % 3600) / 60)
            
            banned_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_at))
            
            if remaining > 0:
                return f"账号封禁中（封禁时间: {banned_time}，已封禁 {hours} 小时 {minutes} 分钟，{remaining_hours} 小时 {remaining_minutes} 分钟后再检测）"
            else:
                return f"账号封禁中（封禁时间: {banned_time}，已封禁 {hours} 小时 {minutes} 分钟，正在检测中...）"
        else:
            return "账号封禁中，请等待解封"

    # ==========================================================
    # 状态检查工具
    # ==========================================================
    def is_ready_for_search(self):
        """
        搜索、下单前快速判断账号是否可用
        """
        # API请求不一定依赖浏览器
        # if self.state.get("browser_status") != "running":
        #     return False, "浏览器未运行"
        if self.is_logging:
            return False, "登录中"

        if not self.is_login:
            return False, "未登录"
        # ✅ 检查封禁状态（提前拒绝，避免不必要的API调用）
        # 注意：权威检查在 goofish_api.request 中，这里仅作为提前拒绝
        if self.is_banned():
            banned_at = self.state.get("banned_at")
            if banned_at:
                banned_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_at))
                return False, f"账号被封禁（封禁时间: {banned_time}）"
            else:
                return False, "账号被封禁"
        
        work_status = self.state.get("work_status")
        if work_status not in ["idle", "error"]:
            # ✅ 改进：当状态是error时，返回last_error（不重复包装）
            if work_status == "error":
                last_error = self.state.get("last_error", "未知错误")
                # ✅ 如果last_error已经包含完整的错误信息，直接返回（避免重复包装）
                # 提取核心错误信息（去掉"下单失败:"、"API调用失败:"等前缀）
                if ":" in last_error:
                    # 尝试提取最后一个冒号后的内容作为核心错误信息
                    parts = last_error.split(":")
                    if len(parts) > 1:
                        # 提取最后一个有意义的部分
                        core_error = parts[-1].strip()
                        # 如果包含错误码，提取错误码和描述
                        if "FAIL_BIZ" in core_error or "FAIL_SYS" in core_error:
                            # 提取错误码和描述
                            if "::" in core_error:
                                error_code_part = core_error.split("::")[0]
                                error_desc = core_error.split("::")[-1]
                                # 尝试解析JSON格式的描述
                                try:
                                    import json
                                    desc_json = json.loads(error_desc)
                                    if "toastMsg" in desc_json:
                                        return False, desc_json["toastMsg"]
                                except:
                                    pass
                                return False, f"{error_code_part}: {error_desc[:50]}"
                            return False, core_error[:100]  # 限制长度
                        return False, core_error[:100]
                return False, last_error[:100]  # 限制长度，避免过长
            else:
                return False, f"当前工作状态: {work_status}（需要 idle 或 searching 状态）"
        return True, "OK"
    
    def is_ready_for_order(self):
        """检查是否可以下单"""
        if self.state.get("browser_status") != "running":
            return False, "浏览器未运行"
        if not self.is_login:
            return False, "未登录"
        # ✅ 检查封禁状态（提前拒绝，避免不必要的API调用）
        # 注意：权威检查在 goofish_api.request 中，这里仅作为提前拒绝
        if self.is_banned():
            banned_at = self.state.get("banned_at")
            if banned_at:
                banned_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_at))
                return False, f"账号被封禁（封禁时间: {banned_time}）"
            else:
                return False, "账号被封禁"
        
        work_status = self.state.get("work_status")
        if work_status not in ["idle", "ordering"]:
            # ✅ 改进：当状态是error时，返回last_error（不重复包装）
            if work_status == "error":
                last_error = self.state.get("last_error", "未知错误")
                # ✅ 如果last_error已经包含完整的错误信息，直接返回（避免重复包装）
                # 提取核心错误信息（去掉"下单失败:"、"API调用失败:"等前缀）
                if ":" in last_error:
                    # 尝试提取最后一个冒号后的内容作为核心错误信息
                    parts = last_error.split(":")
                    if len(parts) > 1:
                        # 提取最后一个有意义的部分
                        core_error = parts[-1].strip()
                        # 如果包含错误码，提取错误码和描述
                        if "FAIL_BIZ" in core_error or "FAIL_SYS" in core_error:
                            # 提取错误码和描述
                            if "::" in core_error:
                                error_code_part = core_error.split("::")[0]
                                error_desc = core_error.split("::")[-1]
                                # 尝试解析JSON格式的描述
                                try:
                                    import json
                                    desc_json = json.loads(error_desc)
                                    if "toastMsg" in desc_json:
                                        return False, desc_json["toastMsg"]
                                except:
                                    pass
                                return False, f"{error_code_part}: {error_desc[:50]}"
                            return False, core_error[:100]  # 限制长度
                        return False, core_error[:100]
                return False, last_error[:100]  # 限制长度，避免过长
            else:
                return False, f"当前工作状态: {work_status}（需要 idle 或 ordering 状态）"
        return True, "OK"
